Skip to content

feat: add scroll container prop#1166

Open
DaffPunks wants to merge 1 commit into
mainfrom
scroll-container-prop
Open

feat: add scroll container prop#1166
DaffPunks wants to merge 1 commit into
mainfrom
scroll-container-prop

Conversation

@DaffPunks

@DaffPunks DaffPunks commented Jun 30, 2026

Copy link
Copy Markdown

If editor placed in scrollable container, this happens:

2026-06-30.21.53.49.mov

Because isStickyActive depends on window.addEventListener('scroll', ...). When window is not scrollable, toolbar don't add class: g-md-editor-sticky_sticky-active

Untitled - Frame 1 (5)

Now, with scrollContainerRef if behaves like this:
https://preview.gravity-ui.com/md-editor/1166/?path=/story/experiments-sticky-toolbar-in-scroll-container--story


Summary by Sourcery

Add support for sticky toolbars that work inside a custom scroll container instead of only the browser window.

New Features:

  • Introduce an optional scrollContainerRef prop across editor and toolbar views to enable sticky behavior within a specified scrollable container.
  • Add a demo Storybook story showcasing a markdown editor with a sticky toolbar inside a scrollable container.

Enhancements:

  • Extend the useSticky hook to listen to scroll-related events on a provided container element and compute stickiness relative to that container.
  • Adjust MarkdownEditorView styling to handle layouts when a custom scroll container is used via a dedicated modifier class.

@sourcery-ai

sourcery-ai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Reviewer's Guide

Add support for sticky toolbars inside a custom scroll container by extending useSticky to accept a scrollContainerRef, wiring the prop through editor view/toolbar components, and documenting the behavior with a new Storybook experiment.

File-Level Changes

Change Details Files
Extend sticky behavior hook to work with either the window or a provided scroll container element.
  • Introduce CONTAINER_EVENTS set and a getTarget helper to route scroll-related events to either window or the scroll container.
  • Add optional scrollContainerRef parameter to useSticky and compute naturalTopFromContainer based on element and container bounding rectangles.
  • Adjust stickyActive calculation to use scrollContainer.scrollTop when a container is provided, while preserving existing window-based logic as fallback.
  • Update event listener registration and cleanup to use getTarget instead of hard-coded window.
packages/editor/src/react-utils/useSticky.ts
Propagate scrollContainerRef through editor view, toolbar, and settings components so sticky toolbars can track container scrolling.
  • Add optional scrollContainerRef to ViewProps, ToolbarViewProps, MarkupEditorViewProps, and WysiwygEditorViewProps type definitions.
  • Pass scrollContainerRef from MarkdownEditorView through EditorWrapper, toolbar render paths, Settings, and ToolbarView components.
  • Use scrollContainerRef when calling useSticky in Settings and ToolbarView so sticky state is based on container scroll.
  • Add withScrollContainer modifier class when scrollContainerRef is present and define corresponding SCSS rule to relax height constraints.
packages/editor/src/bundle/MarkdownEditorView.tsx
packages/editor/src/bundle/ToolbarView.tsx
packages/editor/src/bundle/MarkupEditorView.tsx
packages/editor/src/bundle/WysiwygEditorView.tsx
packages/editor/src/bundle/MarkdownEditorView.scss
Add a Storybook demo showcasing sticky toolbar behavior inside a scrollable container.
  • Create EditorWithStickyInContainer component that wraps MarkdownEditorView in a fixed-height scrollable div and passes scrollContainerRef to the editor.
  • Initialize a markdown editor instance with rich initial content to ensure scrolling within the container.
  • Register a new Storybook story under 'Experiments / Sticky Toolbar in Scroll Container' using EditorWithStickyInContainer.
demo/src/stories/experiments/sticky-toolbar/Editor.tsx
demo/src/stories/experiments/sticky-toolbar/StickyToolbar.stories.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The naturalTopFromContainer value is computed only once on mount, so if the layout or container padding changes after initial render the sticky threshold will be off; consider recalculating it inside observe() or when relevant reflow events fire.
  • The scrollContainer reference and getTarget are captured once in useEffectOnce, so if scrollContainerRef.current ever changes (e.g. different container or ref reassignment) listeners will still be attached to the old target; if that’s a valid use case, you may want to re-run the effect when the ref changes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `naturalTopFromContainer` value is computed only once on mount, so if the layout or container padding changes after initial render the sticky threshold will be off; consider recalculating it inside `observe()` or when relevant reflow events fire.
- The `scrollContainer` reference and `getTarget` are captured once in `useEffectOnce`, so if `scrollContainerRef.current` ever changes (e.g. different container or ref reassignment) listeners will still be attached to the old target; if that’s a valid use case, you may want to re-run the effect when the ref changes.

## Individual Comments

### Comment 1
<location path="packages/editor/src/react-utils/useSticky.ts" line_range="60" />
<code_context>
-            const refPageOffset = elemRef.current.getBoundingClientRect().top;
-            const stickyOffset = parseInt(getComputedStyle(elemRef.current).top, 10);
-            const stickyActive = refPageOffset <= stickyOffset;
+            const stickyOffset = parseInt(getComputedStyle(elemRef.current).top);
+
+            let stickyActive: boolean;
</code_context>
<issue_to_address>
**nitpick:** Explicitly pass a radix to `parseInt` for clarity and robustness

The previous code used `parseInt(..., 10)`; omitting the radix now relies on environment defaults and may conflict with lint rules. Restoring `10` keeps the behavior explicit and consistent across environments.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread packages/editor/src/react-utils/useSticky.ts Outdated
@gravity-ui

gravity-ui Bot commented Jun 30, 2026

Copy link
Copy Markdown

Storybook Deployed

@gravity-ui

gravity-ui Bot commented Jun 30, 2026

Copy link
Copy Markdown

🎭 Playwright Report

@DaffPunks DaffPunks force-pushed the scroll-container-prop branch from def845c to 0cea852 Compare June 30, 2026 18:50
@DaffPunks DaffPunks force-pushed the scroll-container-prop branch from 0cea852 to 03e6fe7 Compare June 30, 2026 18:57
Comment on lines +25 to +30
const naturalTopFromContainer =
scrollContainer && elemRef.current
? elemRef.current.getBoundingClientRect().top -
scrollContainer.getBoundingClientRect().top -
scrollContainer.clientTop
: null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to move this to the observe()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, move this story to demo/src/stories/examples

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants